# Объекты Task и Future. 
# Класс Future
# Помимо класса Task, в asyncio есть еще один класс, который является родительским для Task - это Future . 
# Начиная с версии 3.7 прямое использование задач (Task) предпочтительно в большинстве случаев. 
# Однако внутри asyncio используются оба класса, так что немного остановимся и на классе Future. 

# Future представляет результат асинхронной операции, которая еще не завершена, но уже ожидается. 

# На текущий момент создать объект Future с помощью стандартных методов asyncio не получится, 
# поэтому для демонстрации работы с такими объектами используем прямое создание объекта класса Future: 

# future = asyncio.Future() 
# Объект Future всегда следует запускать с оператором await, точно так же, как и объект задачи или корутину.

# Аналогия:
# Вы работаете в офисе и у вас есть задача собрать отчеты от всех сотрудников. Вы можете подойти к каждому сотруднику 
# по очереди, ждать, пока он подготовит отчет, и затем переходить к следующему (синхронный подход). Или же вы можете 
# одновременно запросить отчеты у всех сотрудников, и пока они готовят свои отчеты, вы можете заниматься другими делами 
# (асинхронный подход). Второй подход явно эффективнее, не так ли? 
###########################################################
# В этом примере объектом Future является обещание сотрудника предоставить вам отчёт вовремя. А если в организации
# каждый сотрудник должен предоставлять отчёт строго в определённое время, то в этом случае объектом  Future является
# ваше знание об этом факте. И вы как руководитель можете планировать дальше рабочие процессы, основываясь на отчётах,
# но не зная их точного содержимого.
###########################################################
# Метод result() для объектов Future/Task
# Важное преимущество объектов Future/Task - возможность получения результата выполнения корутины после завершения задачи. Мы 
# ранее говорили, что корутина, как и обычная функция, может принимать аргументы и возвращать результаты. Когда мы оборачиваем
# корутину в задачу, результат выполнения корутины мы можем получить через метод result().

# import asyncio

# async def example_task():
#     return "Задача выполнена"


# async def main():
#     task = asyncio.create_task(example_task())  # создание задачи из корутины example_task()
#     await task  # запуск задачи и ожидание выполнения
#     result = task.result()  # сохранение результата в переменную result
#     print(result)

# asyncio.run(main())
#########################
# Установка результата для объектов Future. Метод set_result()
# Одно из отличий Future от Task - это возможность задавать результат для объекта Future через метод set_result(). 
# Данный метод может быть полезен при отладке программы. Для объектов Task данный метод не применим. 
# import asyncio


# async def simulate_long_running_task(name, delay, future: asyncio.Future):
#     print(f"Задача '{name}' началась, будет выполнена за {delay} секунд.")
#     await asyncio.sleep(delay)
#     result = f"Результат задачи '{name}'"
#     print(f"Задача '{name}' завершена.")
#     future.set_result(result)  # Устанавливаем результат для Future объекта


# async def main():
#     # Создаем объект Future
#     future = asyncio.Future()

#     # Запускаем корутину, передаем Future объект в функцию
#     await simulate_long_running_task("Задача1", 3, future)

#     # Получаем результат выполнения задачи
#     result = future.result()
#     print(f"Результат Future: {result}")


# asyncio.run(main())

# Вывод:

# Задача 'Задача1' началась, будет выполнена за 3 секунд.
# Задача 'Задача1' завершена.
# Результат Future: Результат задачи 'Задача1'
# В этом примере функция simulate_long_running_task() принимает объект Future в качестве аргумента и устанавливает
# его результат после выполнения асинхронной операции. 
####################################
# Аналогия:
# Вы являетесь менеджером строительной компании, и ваша задача - обеспечить своевременное выполнение всех строительных
# проектов. В данной аналогии объекты Future  могут быть представлены как обещания или ожидания относительно внешних
# условий или событий, которые влияют на продолжение строительных работ.

# Ваша компания ожидает поставки крупной партии строительных материалов от поставщика. Вы не можете продолжить работу
# над проектом до тех пор, пока материалы не будут доставлены. В этом случае ожидание поставки материалов аналогично
# объекту Future. (т.к. скорее всего вы знаете, какие материалы вам привезут, но без них вы не можете продолжать работу над проектом).

# import asyncio


# async def wait_for_materials(delivery_time, future: asyncio.Future):
#     """
#     Асинхронная функция, имитирующая ожидание доставки строительных материалов.
#     delivery_time - время, необходимое для доставки материалов.
#     """

#     print(f"Ожидание доставки материалов. Доставка займет {delivery_time} секунд.")
#     await asyncio.sleep(delivery_time)  # Имитация задержки доставки
#     print("Материалы доставлены.")
#     future.set_result("Доставка завершена")


# async def manage_construction_project():
#     """
#     Менеджер строительного проекта, который ожидает поставку материалов,
#     прежде чем продолжить работу над проектом.
#     """

#     # Создание Future объекта
#     future_materials_delivery = asyncio.Future()

#     # Инициирование ожидания доставки материалов
#     asyncio.create_task(wait_for_materials(5, future_materials_delivery))

#     # Вы можете здесь добавить другие задачи, которые могут выполняться асинхронно
#     # Ожидание результата Future
#     await future_materials_delivery

#     # Получение и вывод результата доставки
#     delivery_result = future_materials_delivery.result()
#     print(f"Результат доставки: {delivery_result}")

#     # После доставки можно продолжить работы над проектом
#     print("Продолжение строительных работ.")


# asyncio.run(manage_construction_project())

# В этом коде wait_for_materials()  принимает дополнительный аргумент — объект Future, и устанавливает его
# результат после имитации задержки доставки. Функция manage_construction_project() создает Future объект и передает
# его в wait_for_materials(), а затем ожидает его завершения. Этот подход демонстрирует явное использование Future для
# управления асинхронными операциями в asyncio.
#################################
# Объект Future представляет собой контракт, который обязует поставщика предоставить материалы в определенное время. Когда 
# поставка будет осуществлена, объект Future обновляется, и вы можете продолжить управлять строительными работами, опираясь
# на выполненное обещание.
#################################
# Использование результатов выполнения корутин.
# Во многих случаях результат выполнения одной корутины нужно использовать в качестве аргумента для выполнения второй корутины. 
# В этом случае нужно выполнять корутины последовательно, дожидаясь выполнения первой корутины. А полученный результат передавать
# в качестве аргумента для второй корутины. 

# Ниже приведен пример такой ситуации. Так как данный урок посвящен объектам Future, мы не будем возвращать результат
# корутины стандартным способом, а для примера воспользуемся методом set_result() объектов Future. 

import asyncio


async def do_some_work_1(x, future: asyncio.Future):
    print(f"Выполняется работа 1: {x}")
    await asyncio.sleep(x)
    future.set_result(x * 2)


async def do_some_work_2(x, future: asyncio.Future):
    print(f"Выполняется работа 2: {x}")
    await asyncio.sleep(x)
    future.set_result(x + 2)


async def main():
    # Создаем объекты Future для каждой задачи
    future_1 = asyncio.Future()
    future_2 = asyncio.Future()

    # Запускаем первую задачу и передаем ей Future
    asyncio.create_task(do_some_work_1(2, future_1))

    # Дожидаемся завершения первой задачи
    await future_1
    result_1 = future_1.result()

    # Запускаем вторую задачу, передавая результат первой и объект Future
    asyncio.create_task(do_some_work_2(result_1, future_2))

    # Дожидаемся завершения второй задачи
    await future_2
    result_2 = future_2.result()

    print(f"Результат future_1: {result_1}")  # Выводим результат первой задачи
    print(f"Результат future_2: {result_2}")  # Выводим результат второй задачи


asyncio.run(main())


# Вывод:

# Выполняется работа 1: 2
# Выполняется работа 2: 4
# Результат future_1: 4
# Результат future_2: 6

#####################################
# В этом примере функции do_some_work_1() и do_some_work_2() устанавливают результат выполнения
# задачи напрямую в объекты Future. Мы используем asyncio.create_task() для запуска корутин, которые
# теперь принимают объект Future в качестве аргумента и устанавливают его результат по завершении.
# Это демонстрирует более низкоуровневое взаимодействие с asyncio.Future, хотя в обычной практике
# разработки такой подход используется реже по сравнению с использованием async/await и asyncio.create_task()
# для управления асинхронными операциями.